package reit.ass3;

import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.log4j.Logger;

/**
 * The Class RunnableCustomerGroupManager.
 */
class RunnableCustomerGroupManager implements Runnable {

	/** The management. */
	private Management fManagement;

	/** The customer group details. */
	private CustomerGroupDetails fCustomerGroupDetails;

	/** The rental requsts queue. */
	private Queue<RentalRequest> fRentalRequstsQueue;

	/** The current rental request. */
	private RentalRequest fCurrentRentalRequest;

	/** The number of rental requests. */
	private AtomicInteger fNumRentalRequests;

	/** The logger. */
	private Logger fLogger;

	/** The customer groups staying. */
	private AtomicInteger fCustomerGroupsStaying;

	/** The number of open rental requests. */
	private AtomicInteger fNumOfOpenRentalRequests;

	/**
	 * Instantiates a new runnable customer group manager.
	 *
	 * @param management the management
	 * @param customerGroupDetails the customer group details
	 * @param NumRentalRequests the num rental requests
	 * @param ManagersWhoDidNotFinishedDamageReports the managers who did not finished damage reports
	 * @param numOfOpenRentalRequests the num of open rental requests
	 */
	public RunnableCustomerGroupManager(final Management management,
			final CustomerGroupDetails customerGroupDetails,
			final AtomicInteger NumRentalRequests,final AtomicInteger ManagersWhoDidNotFinishedDamageReports , final AtomicInteger numOfOpenRentalRequests) {
		fLogger = Logger.getLogger(RunnableCustomerGroupManager.class);
		fNumRentalRequests = NumRentalRequests;
		this.fManagement = management;
		this.fCustomerGroupsStaying = new AtomicInteger();
		this.fCustomerGroupDetails = customerGroupDetails;
		this.fRentalRequstsQueue = this.fCustomerGroupDetails
				.getRentalRequests();
		this.fNumOfOpenRentalRequests = numOfOpenRentalRequests;
		this.fCurrentRentalRequest = this.fRentalRequstsQueue.remove();
	}

	/* (non-Javadoc)
	 * @see java.lang.Runnable#run()
	 */
	@Override
	/**
	 * @description while it is has rentalrequests it hasnt completed submits rentalrequest to management 
	 * waits for fcurrentRentalRentalRequest to be fufilled and simulategroups stay in asset
	 * wait until stay is complete combine damage of all customers and genrate damagereport send it to management
	 * @pre all fields != null
	 * 
	 * */
	public void run() {
		this.fLogger.info(MyStringBuilder.buildString("Group has ",
				Integer.toString(fRentalRequstsQueue.size()), " requests"));
		while ( this.fCurrentRentalRequest != null) {//fRentalRequstsQueue.size() => 0 ||
			this.fLogger.info(MyStringBuilder.buildString(
					this.fCustomerGroupDetails.getGroupManagerName(),
					" has started on request",
					Integer.toString(fCurrentRentalRequest.getId()),
					" remaining "
							+ Integer.toString(fRentalRequstsQueue.size()),
					" requests in group"));
			submitRentalRequest(fCurrentRentalRequest);
			waitForRentalRequestFuffillment();
			this.fLogger.info("Rental request returned to group manager");
			float combinedDamage = OccupyRequest();
			this.fCustomerGroupsStaying.incrementAndGet();
			CompleteRequest();
			this.fLogger.info(MyStringBuilder.buildString("Asset ",
					this.fCurrentRentalRequest.getAsset().getName(),
					" was sent to be tested for repair"));
			createDamageReport(fCurrentRentalRequest.getAsset(), combinedDamage);
			this.fCustomerGroupsStaying.decrementAndGet();
			this.fNumOfOpenRentalRequests.decrementAndGet();
			if (fRentalRequstsQueue.size()>0){
				this.fCurrentRentalRequest = this.fRentalRequstsQueue.poll(); // start working on next rentalrequest
			}
			else{
				this.fCurrentRentalRequest = null;
			}
		}
		this.fLogger.info(MyStringBuilder.buildString(
				this.fCustomerGroupDetails.getGroupManagerName(),
				" has finished all its rental requests"));
	}



	/**
	 * Complete request.
	 */
	public void CompleteRequest() {

		this.fCurrentRentalRequest.setStatusTo(Status.COMPLETE);
		this.fNumRentalRequests.decrementAndGet();
		this.fManagement.collectRent(fCurrentRentalRequest.getAsset()
				.getCostPerNight() * fCurrentRentalRequest.getDuration() * this.fCustomerGroupDetails.getCustomers().size());
		this.fLogger.info(MyStringBuilder.buildString(
				"status changed to Complete on asset request ",
				this.fCurrentRentalRequest.getAsset().getName()));

	}

	/**
	 * Occupy request.
	 *
	 * @return the float
	 */
	public float OccupyRequest() {
		this.fCurrentRentalRequest.getAsset().setStatusTo(Status.OCCUPIED);
		this.fLogger.info(MyStringBuilder.buildString(
				"status changed to Occupied on asset request ",
				this.fCurrentRentalRequest.getAsset().getName()));
		Float combinedDamage = new Float(
				simulateStayInAssetForRequest(createCallableSimulateStayInAssetForRequest(fCurrentRentalRequest)));
		this.fLogger.info(MyStringBuilder.buildString(
				"causing total damage of ", combinedDamage.toString(), "to ",
				this.fCurrentRentalRequest.getAsset().getName()));
		return combinedDamage.floatValue();
	}

	/**
	 * Submit rental request.
	 *
	 * @param toSubmit the to submit
	 * @description submits rental request to management
	 */

	private void submitRentalRequest(final RentalRequest toSubmit) {
		fManagement.addRentalRequest(toSubmit);
		this.fLogger.info(MyStringBuilder.buildString("Request ",
				this.fCurrentRentalRequest.getId()
				+ " was submitted to clerks as prey"));
	}

	/**
	 * Wait for rental request fuffillment.
	 *
	 * @description guarded block for checking that rentalrequst is fufilled
	 * @category GuardedBlock
	 */
	private void waitForRentalRequestFuffillment() {
		synchronized (fCurrentRentalRequest) {
			while (fCurrentRentalRequest.getStatus() != Status.FULFILLED) {
				try {
					fCurrentRentalRequest.wait();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (IllegalMonitorStateException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			this.fLogger.info("Customer Manager received RequestChange.");
		}
	}

	/**
	 * Simulate stay in asset for request.
	 *
	 * @param callables the callables
	 * @return total damage in asset
	 * @description runs all CallableSimulateStayInAsset in callables
	 */
	private float simulateStayInAssetForRequest(
			final LinkedList<CallableSimulateStayInAsset> callables) {
		float result = 0;
		Executor ex = Executors.newCachedThreadPool();
		CompletionService<Float> cs = new ExecutorCompletionService<Float>(ex);
		for (CallableSimulateStayInAsset c : callables) {
			cs.submit(c);
		}

		for (int i=0; i< callables.size();i++){
			try {
				result = result + cs.take().get();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (ExecutionException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}

		return result;
	}

	/**
	 * Creates the callable simulate stay in asset for request.
	 *
	 * @param rentalRequest the rental request
	 * @return the linked list
	 * @description runs all CallableSimulateStayInAsset for rentalrequst with same id in fCallableSimulateStayInAsset
	 */
	private LinkedList<CallableSimulateStayInAsset> createCallableSimulateStayInAssetForRequest(
			final RentalRequest rentalRequest) {
		LinkedList<CallableSimulateStayInAsset> result = new LinkedList<CallableSimulateStayInAsset>();
		for (Customer c : this.fCustomerGroupDetails.getCustomers()) {
			result.add(new CallableSimulateStayInAsset(c, fCurrentRentalRequest
					.getDuration()));
		}
		return result;
	}

	/**
	 * creates DamageReport.
	 *
	 * @param asset the asset
	 * @param combinedDamage the combined damage
	 */
	private void createDamageReport(final Asset asset, final float combinedDamage) {
		DamageReport report = new DamageReport(asset, combinedDamage);
		this.fCurrentRentalRequest.getAsset().setStatusTo(Status.UNAVAILABLE);
		fManagement.putDamageReport(report);
		this.fLogger.info(MyStringBuilder.buildString(
				"Customer group manager ",
				this.fCustomerGroupDetails.getGroupManagerName(),
				" sent a dammage report to management for asset ",
				asset.getName()));
	}

}
